package com.jxpanda.infrastructure.spring.toolkit;

import com.jxpanda.infrastructure.core.constant.HttpAuthorizationType;
import com.jxpanda.infrastructure.core.tollkit.ReflectionKit;
import lombok.experimental.UtilityClass;
import org.springframework.core.ParameterizedTypeReference;
import org.springframework.http.HttpHeaders;
import org.springframework.http.HttpMethod;
import org.springframework.web.reactive.function.BodyInserters;
import org.springframework.web.reactive.function.client.WebClient;
import org.springframework.web.reactive.function.client.WebClient.RequestHeadersSpec;
import org.springframework.web.util.UriComponentsBuilder;
import reactor.core.publisher.Flux;
import reactor.core.publisher.Mono;

import java.util.HashMap;
import java.util.Map;
import java.util.function.Consumer;
import java.util.function.Function;

/**
 * @author Panda
 */
@UtilityClass
public final class ReactiveHttpKit {

    /**
     * 提供一个静态方法来创建一个Builder实例，用于构建请求。
     * 这是创建Builder实例的推荐方式，它允许逐步配置请求。
     *
     * @return 新创建的Builder实例
     */
    public static Builder builder() {
        return new Builder();
    }

    /**
     * 简化创建一个GET请求的Builder实例的过程。
     * 这种方法预设了请求方法为GET，允许开发者直接配置其他请求属性。
     *
     * @return 配置了GET方法的Builder实例
     */
    public static Builder get() {
        return builder()
                .method(HttpMethod.GET);
    }

    /**
     * 简化创建一个GET请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为GET和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了GET方法和URL的Builder实例
     */
    public static Builder get(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.GET);
    }

    /**
     * 简化创建一个POST请求的Builder实例的过程。
     * 这种方法预设了请求方法为POST，允许开发者直接配置其他请求属性。
     *
     * @return 配置了POST方法的Builder实例
     */
    public static Builder post() {
        return builder()
                .method(HttpMethod.POST);
    }

    /**
     * 简化创建一个POST请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为POST和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了POST方法和URL的Builder实例
     */
    public static Builder post(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.POST);
    }

    /**
     * 简化创建一个PUT请求的Builder实例的过程。
     * 这种方法预设了请求方法为PUT，允许开发者直接配置其他请求属性。
     *
     * @return 配置了PUT方法的Builder实例
     */
    public static Builder put() {
        return builder()
                .method(HttpMethod.PUT);
    }

    /**
     * 简化创建一个PUT请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为PUT和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了PUT方法和URL的Builder实例
     */
    public static Builder put(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.PUT);
    }

    /**
     * 简化创建一个DELETE请求的Builder实例的过程。
     * 这种方法预设了请求方法为DELETE，允许开发者直接配置其他请求属性。
     *
     * @return 配置了DELETE方法的Builder实例
     */
    public static Builder delete() {
        return builder()
                .method(HttpMethod.DELETE);
    }

    /**
     * 简化创建一个DELETE请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为DELETE和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了DELETE方法和URL的Builder实例
     */
    public static Builder delete(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.DELETE);
    }

    /**
     * 简化创建一个OPTIONS请求的Builder实例的过程。
     * 这种方法预设了请求方法为OPTIONS，允许开发者直接配置其他请求属性。
     *
     * @return 配置了OPTIONS方法的Builder实例
     */
    public static Builder options() {
        return builder()
                .method(HttpMethod.OPTIONS);
    }

    /**
     * 简化创建一个OPTIONS请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为OPTIONS和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了OPTIONS方法和URL的Builder实例
     */
    public static Builder options(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.OPTIONS);
    }

    /**
     * 简化创建一个HEAD请求的Builder实例的过程。
     * 这种方法预设了请求方法为HEAD，允许开发者直接配置其他请求属性。
     *
     * @return 配置了HEAD方法的Builder实例
     */
    public static Builder head() {
        return builder()
                .method(HttpMethod.HEAD);
    }

    /**
     * 简化创建一个HEAD请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为HEAD和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了HEAD方法和URL的Builder实例
     */
    public static Builder head(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.HEAD);
    }

    /**
     * 简化创建一个PATCH请求的Builder实例的过程。
     * 这种方法预设了请求方法为PATCH，允许开发者直接配置其他请求属性。
     *
     * @return 配置了PATCH方法的Builder实例
     */
    public static Builder patch() {
        return builder()
                .method(HttpMethod.PATCH);
    }

    /**
     * 简化创建一个PATCH请求到特定URL的Builder实例的过程。
     * 这种方法预设了请求方法为PATCH和目标URL，允许开发者直接配置其他请求属性。
     *
     * @param url 请求的目标URL
     * @return 配置了PATCH方法和URL的Builder实例
     */
    public static Builder patch(String url) {
        return builder()
                .url(url)
                .method(HttpMethod.PATCH);
    }

    /**
     * ReactiveHttpHandler类用于处理HTTP请求并以反应式方式返回响应。
     * 该类通过构造函数接收一个请求头规范，允许后续操作定制HTTP请求，并执行该请求。
     */
    @SuppressWarnings("ClassCanBeRecord")
    public static class ReactiveHttpHandler {

        /**
         * 保存请求头规范的实例，用于构建和定制HTTP请求。
         */
        private final RequestHeadersSpec<?> requestHeadersSpec;

        /**
         * 构造函数，初始化ReactiveHttpHandler实例。
         *
         * @param requestHeadersSpec 请求头规范，用于定制HTTP请求。
         */
        private ReactiveHttpHandler(RequestHeadersSpec<?> requestHeadersSpec) {
            this.requestHeadersSpec = requestHeadersSpec;
        }

        /**
         * 执行Web客户端请求并检索响应。
         * <p>
         * 此方法封装了对实际HTTP请求的发起，并返回处理响应的规范，允许进一步处理响应体、头等。
         * 它是WebClient中用于发起请求并获取响应的一个关键方法。
         *
         * @return ResponseSpec 对象，用于指定和执行对响应的进一步操作，如断言、转换响应体等。
         */
        public WebClient.ResponseSpec retrieve() {
            // 通过调用requestHeadersSpec的retrieve方法来发起请求并获取响应规范。
            return requestHeadersSpec.retrieve();
        }

        /**
         * 执行HTTP请求并返回响应体作为String类型的Mono。
         * 该方法提供了一个简化的接口，用于执行HTTP请求并以字符串形式处理响应体。
         *
         * @return Mono<String>，表示异步获取的HTTP响应体。
         */
        public Mono<String> execute() {
            return retrieve()
                    .bodyToMono(String.class);
        }

        /**
         * 执行HTTP请求并返回响应体作为指定类型的Mono。
         * 该方法允许用户指定响应体的解析类型，提供了灵活的响应处理方式。
         *
         * @param clazz 响应体数据应转换为的类类型。
         * @param <T>   响应体数据的泛型类型。
         * @return Mono<T>，表示异步获取的、已转换为目标类型HTTP响应体。
         */
        public <T> Mono<T> execute(Class<T> clazz) {
            return retrieve()
                    .bodyToMono(clazz);
        }

        /**
         * 执行HTTP请求并返回响应体作为指定类型的Mono。
         * 该方法允许用户指定响应体的解析类型，提供了灵活的响应处理方式。
         *
         * @param bodyMapper 响应体数据的映射函数
         * @param <T>        响应体数据的泛型类型。
         * @return Mono<T>，表示异步获取的、已转换为目标类型HTTP响应体。
         */
        public <T> Mono<T> execute(Function<String, T> bodyMapper) {
            return retrieve()
                    .bodyToMono(String.class)
                    .map(bodyMapper);
        }

        /**
         * 使用ParameterizedTypeReference执行请求并返回Mono响应。
         * <p>
         * 这个方法允许消费者指定一个泛型类型，以获取API响应的单个实例。
         * 参数：
         * <ul>
         * <li>{@code typeReference} - 一个ParameterizedTypeReference，用于指定响应的泛型类型。</li>
         * </ul>
         * 返回值：
         * <ul>
         * <li>{@code Mono<T>} - 一个Mono实例，用于异步处理单个响应项。</li>
         * </ul>
         *
         * @param <T>           响应数据的泛型类型。
         * @param typeReference 用于指定响应数据类型的ParameterizedTypeReference。
         * @return 一个Mono实例，用于异步获取单个响应项。
         */
        public <T> Mono<T> execute(ParameterizedTypeReference<T> typeReference) {
            return retrieve()
                    .bodyToMono(typeReference);
        }

        /**
         * 执行请求并返回字符串的Flux响应。
         * <p>
         * 这个方法用于获取API响应作为一个字符串流。
         *
         * @return Flux<String> - 一个Flux实例，用于异步处理多个字符串响应项。
         */
        public Flux<String> executeToFlux() {
            return retrieve().bodyToFlux(String.class);
        }

        /**
         * 使用类类型执行请求并返回Flux响应。
         * <p>
         * 这个方法允许消费者指定一个类类型，以获取API响应的多个实例。
         * 参数：
         * <ul>
         * <li>{@code clazz} - 一个类对象，用于指定响应的类型。</li>
         * </ul>
         * 返回值：
         * <ul>
         * <li>{@code Flux<T>} - 一个Flux实例，用于异步处理多个响应项。</li>
         * </ul>
         *
         * @param <T>   响应数据的泛型类型。
         * @param clazz 用于指定响应数据类型的类对象。
         * @return 一个Flux实例，用于异步获取多个响应项。
         */
        public <T> Flux<T> executeToFlux(Class<T> clazz) {
            return retrieve().bodyToFlux(clazz);
        }

        /**
         * 使用ParameterizedTypeReference执行请求并返回Flux响应。
         * <p>
         * 这个方法允许消费者指定一个泛型类型，以获取API响应的多个实例。
         * 参数：
         * <ul>
         * <li>{@code typeReference} - 一个ParameterizedTypeReference，用于指定响应的泛型类型。</li>
         * </ul>
         * 返回值：
         * <ul>
         * <li>{@code Flux<T>} - 一个Flux实例，用于异步处理多个响应项。</li>
         * </ul>
         *
         * @param <T>           响应数据的泛型类型。
         * @param typeReference 用于指定响应数据类型的ParameterizedTypeReference。
         * @return 一个Flux实例，用于异步获取多个响应项。
         */
        public <T> Flux<T> executeToFlux(ParameterizedTypeReference<T> typeReference) {
            return retrieve().bodyToFlux(typeReference);
        }

    }

    /**
     * Builder类用于构建ReactiveHttpHandler实例，通过链式调用方法来配置HTTP请求的各个属性。
     */
    public static class Builder {
        private String baseUrl; // 请求的基础URL
        private HttpMethod method; // HTTP请求方法
        private Object body; // HTTP请求的主体内容
        private final Map<String, String> queryParams = new HashMap<>(8); // 查询参数
        private final Map<String, String> headers = new HashMap<>(8); // HTTP请求头

        /**
         * 设置请求的基础URL。
         *
         * @param url 请求的基础URL
         * @return 当前Builder实例
         */
        public Builder url(String url) {
            this.baseUrl = url;
            return this;
        }

        /**
         * 设置HTTP请求方法。
         *
         * @param method HTTP请求方法枚举
         * @return 当前Builder实例
         */
        public Builder method(HttpMethod method) {
            this.method = method;
            return this;
        }

        /**
         * 通过字符串设置HTTP请求方法。
         *
         * @param method HTTP请求方法的字符串表示（如"GET", "POST"）
         * @return 当前Builder实例
         */
        public Builder method(String method) {
            this.method = HttpMethod.valueOf(method.trim().toUpperCase());
            return this;
        }

        /**
         * 设置HTTP请求的主体内容。
         *
         * @param body HTTP请求的主体内容
         * @return 当前Builder实例
         */
        public Builder body(Object body) {
            this.body = body;
            return this;
        }

        /**
         * 把查询参数添加到Builder实例中。
         *
         * @param object 查询参数(对象)
         * @return 当前Builder实例
         */
        public Builder query(Object object) {
            this.queryParams.putAll(ReflectionKit.objectToMap(object));
            return this;
        }

        /**
         * 把查询参数添加到Builder实例中。
         *
         * @param queryParams 查询参数
         * @return 当前Builder实例
         */
        public Builder query(Map<String, String> queryParams) {
            this.queryParams.putAll(queryParams);
            return this;
        }

        /**
         * 通过Consumer函数式接口动态设置查询参数。
         * 与query函数的区别是，这个函数可以获取到this.queryParams对象，可以进行一定程度的修改
         *
         * @param queryHandler 用于修改查询参数的Consumer实例
         * @return 当前Builder实例
         */
        public Builder queryHandler(Consumer<Map<String, String>> queryHandler) {
            queryHandler.accept(this.queryParams);
            return this;
        }

        /**
         * 通过Consumer函数式接口动态设置HTTP请求头。
         *
         * @param headersHandler 用于修改请求头的Consumer实例
         * @return 当前Builder实例
         */
        public Builder headersHandler(Consumer<Map<String, String>> headersHandler) {
            headersHandler.accept(this.headers);
            return this;
        }

        /**
         * 设置Content-Type请求头。
         *
         * @param contentType 请求的Content-Type值
         * @return 当前Builder实例
         */
        public Builder contentType(String contentType) {
            return headersHandler(it -> {
                it.put(HttpHeaders.CONTENT_TYPE, contentType);
            });
        }

        /**
         * 根据授权类型和令牌设置Authorization请求头。
         *
         * @param authorizationType 授权类型
         * @param token             令牌
         * @return 当前Builder实例
         */
        public Builder authorization(HttpAuthorizationType authorizationType, String token) {
            return headersHandler(it -> {
                it.put(HttpHeaders.AUTHORIZATION, authorizationType.build(token));
            });
        }

        /**
         * 简化设置Bearer类型的Authorization请求头。
         *
         * @param token 令牌
         * @return 当前Builder实例
         */
        public Builder bearer(String token) {
            return authorization(HttpAuthorizationType.BEARER, token);
        }

        /**
         * 简化设置Basic类型的Authorization请求头。
         *
         * @param username 用户名
         * @param password 密码
         * @return 当前Builder实例
         */
        public Builder basic(String username, String password) {
            return authorization(HttpAuthorizationType.BASIC, username + ":" + password);
        }

        /**
         * 简化设置API Key类型的Authorization请求头。
         *
         * @param token API Key
         * @return 当前Builder实例
         */
        public Builder apiKey(String token) {
            return authorization(HttpAuthorizationType.API_KEY, token);
        }

        /**
         * 构建ReactiveHttpHandler实例。
         * 根据配置的基础URL、方法、主体内容和请求头等信息，创建并返回一个ReactiveHttpHandler实例。
         *
         * @return 构建的ReactiveHttpHandler实例
         */
        public ReactiveHttpHandler build() {
            WebClient webClient = WebClient.create();

            // 构建请求
            WebClient.RequestBodyUriSpec requestSpec = webClient.method(method == null ? HttpMethod.GET : method);

            // 动态设置URL和查询参数
            UriComponentsBuilder uriBuilder = UriComponentsBuilder.fromUriString(baseUrl);
            if (!queryParams.isEmpty()) {
                queryParams.forEach(uriBuilder::queryParam);
            }
            requestSpec.uri(uriBuilder.build().toUriString());

            // 动态设置请求头
            if (!headers.isEmpty()) {
                headers.forEach(requestSpec::header);
            }

            // 动态设置请求体（如果存在）
            if (body != null) {
                requestSpec.body(BodyInserters.fromValue(body));
            }

            return new ReactiveHttpHandler(requestSpec);
        }

    }

}